-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deconstruction-assignment for tuples #11457
Conversation
@@ -1737,6 +1744,52 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax | |||
|
|||
var checkedVariables = checkedVariablesBuilder.ToImmutableAndFree(); | |||
|
|||
// tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())` | |||
if (boundRHS.Kind == BoundKind.TupleLiteral || ((object)boundRHS.Type != null && boundRHS.Type.IsTupleType)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the first condition necessary or would a literal also satisfy the second condition?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for the first condition is tuple literals that don't have a natural type, such as (null, null)
.
if (boundRHS.Type == null) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_DeconstructRequiresExpression, node); | ||
return BadExpression(node, new[] { boundRHS }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using overload BadExpression(CSharpSyntaxNode, BoundNode)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Done
I just pushed a change to address Chuck's feedback so far. |
} | ||
|
||
[Fact] | ||
public void DeconstructIL() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a subset of the Deconstruct
test above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is now that I added VerifyIL to the test above. I'll remove the redundant one.
LGTM |
|
||
private ImmutableArray<BoundExpression> AccessTupleFields(BoundDeconstructionAssignmentOperator node, BoundExpression loweredRight, ArrayBuilder<LocalSymbol> temps, ArrayBuilder<BoundExpression> stores) | ||
{ | ||
var tupleType = loweredRight.Type.IsTupleType ? loweredRight.Type : TupleTypeSymbol.Create((NamedTypeSymbol)loweredRight.Type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this function is never called when IsTupleType is false. Consider simply asserting this instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This is un-necessary now that the conversion is applied to all tuple cases. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out I was wrong. Tests won't pass without this logic. The key is that the node on the right may have a tuple type before lowering, but after lowering it becomes a tuple constructor call (which returns underlying type).
// (x, y) = (1, 2); | ||
Diagnostic(ErrorCode.ERR_NoImplicitConv, "(1, 2)").WithArguments("(int, int)", "(byte, string)").WithLocation(9, 18) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding deconstruction tests for tuples on the right that have names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test with names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add tests where RHS is used on the left:
(object i, object ii) x = (1,2);
object y;
(x.ii, y) = x;
should result in x: {1, 1} , y: 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add the one you suggested. Note that I already have the one Mads suggested (x, y) = (y, x);
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also try a variant of above:
(object i, object ii) x = (1,2);
object y;
ref var a = ref x;
(a.ii, y) = x;
should result in x: {1, 1} , y: 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are some cases where you need a temp for the RHS even when it is a local/parameter.
@@ -397,6 +397,19 @@ public virtual FieldSymbol TupleUnderlyingField | |||
} | |||
} | |||
|
|||
/// <summary> | |||
/// If this is a field of a tuple type, | |||
/// id is an index of the element (zero-based). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably shouldn't mention Id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, should probably say "If this is a field representing a tuple element", rather than "If this is a field of a tuple type", because some fields of a tuple type return negative numbers.
In reply to: 64289994 [](ancestors = 64289994)
f49cbfe
to
a5e4fc6
Compare
@AlekseyTs Pushed the latest change with doc tweaks and improving the logic to collect fields from tuple symbol. |
a5e4fc6
to
66662a3
Compare
private ImmutableArray<FieldSymbol> CollectTupleElementFields() | ||
{ | ||
var builder = ArrayBuilder<FieldSymbol>.GetInstance(_elementTypes.Length); | ||
builder.SetItem(_elementTypes.Length - 1, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is subtle, I think you can simply pass null as the second parameter for ArrayBuilder.GetInstance to have the builder allocate the items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! That's much nicer. Thanks
LGTM, but do consider my last two comments. |
This implementation fails to compile this test: namespace System
{
public struct ValueTuple<T1, T2>
{
public T1 Item1;
public T2 Item2;
public override string ToString()
{
T1 k;
T2 v;
// error: no Destruct instance or extension method was found for ValueTuple<T1, T2>
(k, v) = this;
return $"({k}, {v})";
}
}
} |
@gafter the example that you gave is essentially the same as ValueTuple<T1, T2> t = this;
(k, v) = t; I wonder if that has similar problems. Overall, it would seem acceptable for tuple types to behave differently inside their own definition types, since that scenario is diminishingly small, but I see no reasons why it would be the case here. |
@VSadov It isn't the same, in the current implementation, because inside the body of |
There is a hole in the way we assign the type for |
@gafter interesting. |
please test all |
We can make "this" to be a tuple inside VT definition, or we can make an exception for that case - whatever. |
I don't think we would face a circularity problem the check for tuple-compatible type is "local". |
See also #11530 for another symptom of the same issue. |
test open please |
This PR mainly adds deconstruction-assignment with the right-hand-side being a tuple type or a tuple literal (with or without type).
This also adds tests for follow-ups from previous feedback:
@dotnet/roslyn-compiler for review.